Skip to content

C++ 禁止拷贝与移动的常见原因 ​

C++ 里常把类的拷贝构造/拷贝赋值与移动构造/移动赋值用 = delete 删除。什么时候应该这样做、为什么——本文汇总六类常见原因与实现方式,并澄清一个容易误判的条目(不可变对象并不是删除拷贝的理由)。适合在类设计时对照检查。

常见原因 ​

1. 资源管理的唯一性 ​

  • 场景:类管理独占资源(如文件句柄、网络连接、锁、硬件设备等)。
  • 原因:
    • 拷贝或移动可能导致资源被多个对象共享,引发重复释放或竞争条件。
    • 例如,std::unique_ptr 禁止拷贝,因为指针所有权必须唯一。

2. 单例模式(Singleton) ​

  • 场景:确保全局只有一个实例的类。
  • 原因:
    • 拷贝或移动会破坏单例的唯一性。
    • 通常通过删除拷贝和移动操作来强制实现单例。

3. 性能优化 ​

  • 场景:类的拷贝或移动成本极高(如大型缓存、复杂状态)。
  • 原因:
    • 禁止拷贝/移动可以强制用户显式管理对象生命周期(如通过引用或指针传递)。
    • 避免隐式拷贝导致的性能问题。

4. 线程安全性 ​

  • 场景:类包含共享状态或非线程安全的成员。
  • 原因:
    • 拷贝或移动可能导致多线程环境下状态不一致。
    • 典型例子:含 std::mutex、std::condition_variable 等同步原语成员的类——这些成员本身不可拷贝、不可移动(cppreference:std::mutex is neither copyable nor movable),包含它们的类也会隐式失去拷贝与移动能力。

5. 接口设计约束 ​

  • 场景:抽象基类或接口类。
  • 原因:
    • 禁止拷贝/移动可以强制子类实现自己的语义(如深拷贝)。
    • 避免切片问题(slicing problem)。

6. 避免逻辑错误 ​

  • 场景:类的拷贝或移动无意义或可能导致逻辑错误。
  • 原因:
    • 例如,一个表示「数据库连接」的类,拷贝会导致多个对象共享同一连接,可能引发事务冲突。

容易误判的一点:不可变对象并不是删除拷贝的理由。不可变值对象通常允许拷贝(拷贝不改变唯一性),不可变语义由 const 成员保证;真正需要删除拷贝的是单例式的全局唯一状态,已由第 2 条覆盖。

实现方式 ​

cpp
class NonCopyable {
public:
    NonCopyable() = default;
    ~NonCopyable() = default;

    // 禁止拷贝
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;

    // 禁止移动(C++11 起)
    NonCopyable(NonCopyable&&) = delete;
    NonCopyable& operator=(NonCopyable&&) = delete;
};

使用宏把这套删除集中定义,供多个类复用:

cpp
// 删除拷贝构造与拷贝赋值
#define DISABLE_COPY(ClassName) \
    ClassName(const ClassName&) = delete; \
    ClassName& operator=(const ClassName&) = delete;

// 删除移动构造与移动赋值
#define DISABLE_MOVE(ClassName) \
    ClassName(ClassName&&) = delete; \
    ClassName& operator=(ClassName&&) = delete;

// 删除拷贝/移动构造与拷贝/移动赋值
#define DISABLE_COPY_MOVE(ClassName) \
    DISABLE_COPY(ClassName) \
    DISABLE_MOVE(ClassName)

注意移动构造/移动赋值的参数应写 ClassName&&(非 const 右值引用)。写成 const ClassName&& 虽然按标准也算移动构造函数(能拦住移动),但 const 右值引用属于反惯用法,一般视为笔误。

顺带一提:只声明拷贝操作为 = delete 时,移动操作不会被隐式生成,但显式把四件套全部删除是更明确、不依赖隐式规则的写法。

小结 ​

六类原因可以归纳为三条主线:资源与状态的唯一性(独占资源、单例、线程安全成员)、设计约束(接口类防切片)、性能与逻辑(避免天价拷贝、避免无意义复制)。C++11 的移动语义背景下,「删除拷贝但保留移动」表达独占可转移,「四件套全删」表达完全不可复制——按语义选择即可。相关背景见 C++11 新特性(右值引用与移动语义),系列总览见 现代 C++ 新特性汇总。

最近更新

基于 VitePress 构建